﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace MachineLearning {
    public partial class MainForm : Form {

        NetworkState _netState = null;
        NumericUpDown[] _layerUpDowns = null;

        public MainForm() {
            InitializeComponent();
            pictureBox1.Image = ColorSpectrum.getTestImage();
            _netState = new NetworkState();
            //put the hidden layer numeric up down controlls into a list
            _layerUpDowns = new NumericUpDown[6];
            _layerUpDowns[0] = layer1NumericUpDown;
            _layerUpDowns[1] = layer2NumericUpDown;
            _layerUpDowns[2] = layer3NumericUpDown;
            _layerUpDowns[3] = layer4NumericUpDown;
            _layerUpDowns[4] = layer5NumericUpDown;
            _layerUpDowns[5] = layer6NumericUpDown;
        }

        //user clicked the file->open menu item
        private void openToolStripMenuItem_Click(object sender, EventArgs e) {
            openXmlFileDialog.ShowDialog();
        }

        //user selected a tab or xml file for the program to process
        private void openXmlFileDialog_FileOk(object sender, CancelEventArgs e) {
            if (openXmlFileDialog.FileName.EndsWith(".xml")) {
                _netState.initNetworkAdapter(openXmlFileDialog.FileName);
            } else if (openXmlFileDialog.FileName.EndsWith(".anx")) {
                if (_netState.DataAdaptor == null) {
                    //you must open a data set
                } else {
                    _netState.instantiateNetworkFromFile(openXmlFileDialog.FileName);
                    setControllsPerNetState();
                }
            }
            if (_netState.DataAdaptor != null) {
                if (_netState.DataAdaptor.OpMode == NeuralNetworkDataAdapter.OperationMode.Classify) {
                    if (_netState.NetworkInterface.ANN != null) {
                        classifyButton.Enabled = true;
                    }
                } else if (_netState.DataAdaptor.OpMode == NeuralNetworkDataAdapter.OperationMode.Learn) {
                    if (_netState.NetworkInterface.ANN != null) {
                        classifyButton.Enabled = true;
                        resetStopButton.Enabled = true;
                    } else {
                        learnNetworkButton.Enabled = true;
                    }
                } else {
                    validateButton.Enabled = true;
                }
            }
        }

        //invoked by the timer when the training is finished
        private void finishedProcessing() {
            processAndDisplayOutputValues();
            updateStatusTimer.Enabled = false;
            toolStripProgressBar1.Value = 0;
            toolStripStatusLabel1.Text = "Ready";
            if (_netState.DataAdaptor.OpMode == NeuralNetworkDataAdapter.OperationMode.Learn)
                learnNetworkButton.Enabled = true;
            else if (_netState.DataAdaptor.OpMode == NeuralNetworkDataAdapter.OperationMode.Validate)
                validateButton.Enabled = true;
            classifyButton.Enabled = true;
        }

        //invoked by the timer
        private void updateStatus() {
            meanSquaredErrorTextBox.Text = Math.Round(_netState.getMSE(), 6).ToString();
            derivErrorTextBox.Text = Math.Round(_netState.getDerivMSE(), 4).ToString();
            toolStripProgressBar1.Value = _netState.getCurrentEpoch();
            TimeSpan ts = _netState.ProcessTimer.Elapsed;
            elapsedTimeToolStripStatusLabel.Text
                = ts.Hours + ":" + ts.Minutes + ":" + ts.Seconds;
            this.Text = _netState.getCurrentEpoch()
                + " of " + _netState.TrainingEpochs;
            if (_netState.FinishedProcessing)
                finishedProcessing();
        }

        //update the list view control, contains output vectors
        private void updateListView(double[][] outputVectors) {
            //display the output vectors to the user 
            outputVectorsListView.Clear();
            outputVectorsListView.Columns.Add("Output Vectors");
            for (int i = 0; i < outputVectors[0].Length; i++)
                outputVectorsListView.Columns.Add("[" + i + "]");
            for (int i = 0; i < outputVectors.Length; i++) {
                ListViewItem item = new ListViewItem("vector: " + i);
                for (int j = 0; j < outputVectors[i].Length; j++)
                    item.SubItems.Add(outputVectors[i][j].ToString());
                outputVectorsListView.Items.Add(item);
            }
        }

        //user clicked the learn network button
        private void trainNetworkButton_Click(object sender, EventArgs e) {
            toolStripStatusLabel1.Text = "Learning ...";
            toolStripProgressBar1.Maximum = (int)trainingEpochsNumericUpDown.Value;
            updateStatusTimer.Enabled = true;
            learnNetworkButton.Enabled = false;
            resetStopButton.Enabled = true;
            validateButton.Enabled = false;
            Thread th = new Thread(learnNetwork);
            th.Start();
        }

        //invoked by a thread to learn the network, keeps UI responsive
        private void learnNetwork() {
            _netState.TrainingEpochs = (int)trainingEpochsNumericUpDown.Value;
            _netState.HiddenUnits = new int[(int)hiddenLayersNumericUpDown.Value + 2];
            for (int i = 0; i < _netState.HiddenUnits.Length - 2; i++)
                _netState.HiddenUnits[i + 1] = (int)_layerUpDowns[i].Value;
            _netState.LearningRate = (double)learningRateNumericUpDown.Value;
            _netState.LearningMomentum = (double)momentumNumericUpDown.Value;
            _netState.trainNetwork();
        }

        private void classifyButton_Click(object sender, EventArgs e) {
            toolStripStatusLabel1.Text = "Classifying ...";
            updateStatusTimer.Enabled = true;
            learnNetworkButton.Enabled = false;
            resetStopButton.Enabled = true;
            validateButton.Enabled = false;
            classifyButton.Enabled = false;
            Thread th = new Thread(classifyData);
            th.Start();
        }

        private void classifyData() {
            _netState.classifyInstances();
        }

        private void validateButton_Click(object sender, EventArgs e) {

        }

        //user changed the number of hidden units to use in the network
        private void hiddenLayersNumericUpDown_ValueChanged(object sender, EventArgs e) {
            for (int i = 0; i < _layerUpDowns.Length; i++) {
                if (hiddenLayersNumericUpDown.Value > i)
                    _layerUpDowns[i].Enabled = true;
                else _layerUpDowns[i].Enabled = false;
            }
        }

        private void setControllsPerNetState() {
            trainingEpochsNumericUpDown.Value = _netState.TrainingEpochs;
            hiddenLayersNumericUpDown.Value = _netState.HiddenUnits.Length - 2;
            for (int i = 0; i < _netState.HiddenUnits.Length - 2; i++)
                _layerUpDowns[i].Value = _netState.HiddenUnits[i + 1];
            learningRateNumericUpDown.Value = (decimal)_netState.LearningRate;
            momentumNumericUpDown.Value = (decimal)_netState.LearningMomentum;
        }

        //do some thresholding, rounding, or winner takes all on the output
        private void processAndDisplayOutputValues() {
            if (_netState.NetworkInterface == null || _netState.getOutputVectors() == null) return;
            double[][] processedValues = null;
            double[][] temp = _netState.getOutputVectors();
            //new an array of double arrays
            if (_netState.PostProcessOption != NetworkState.PostProcess.None) {
                processedValues = new double[temp.Length][];
                for (int i = 0; i < processedValues.Length; i++)
                    processedValues[i] = new double[temp[i].Length];
            }
            if (_netState.PostProcessOption == NetworkState.PostProcess.ThresholdToBoolean) {
                double threshold = (double)thresholdNumericUpDown.Value;
                for (int i = 0; i < processedValues.Length; i++) {
                    for (int j = 0; j < processedValues[i].Length; j++) {
                        if (temp[i][j] >= threshold)
                            processedValues[i][j] = 1.0;
                        else processedValues[i][j] = 0.0;
                    }
                }
            } else if (_netState.PostProcessOption == NetworkState.PostProcess.RoundToDecimal) {
                int decimals = (int)decimalsNumericUpDown.Value;
                for (int i = 0; i < processedValues.Length; i++) {
                    for (int j = 0; j < processedValues[i].Length; j++)
                        processedValues[i][j]
                            = Math.Round(temp[i][j], decimals);
                }
            } else if (_netState.PostProcessOption == NetworkState.PostProcess.WinnerTakesAll) {
                double maxValue = 0.0;
                int indexOfMax = 0;
                for (int i = 0; i < processedValues.Length; i++) {
                    for (int j = 0; j < processedValues[i].Length; j++) {
                        if (temp[i][j] > maxValue) {
                            maxValue = temp[i][j];
                            indexOfMax = j;
                        }
                    }
                    for (int j = 0; j < processedValues[i].Length; j++) {
                        if (j == indexOfMax)
                            processedValues[i][j] = maxValue;
                    }
                    maxValue = 0.0;
                }
            }
            if (_netState.PostProcessOption == NetworkState.PostProcess.None)
                updateListView(temp);
            else updateListView(processedValues);
        }

        //update the status strip progress bar and the current MSE
        private void updateStatusTimer_Tick(object sender, EventArgs e) {
            updateStatus();
        }

        //user changed the checked state of a data post processing check box 
        private void postProcessDataCheckBox_CheckedChanged(object sender, EventArgs e) {
            if (sender.GetType() != typeof(CheckBox)) {
                processAndDisplayOutputValues();
                return;
            }
            CheckBox c = (CheckBox)sender;
            if (c.Checked) {
                thresholdCheckBox.Enabled = false;
                decimalsCheckBox.Enabled = false;
                winnerTakesAllCheckBox.Enabled = false;
                if (c == thresholdCheckBox) {
                    thresholdCheckBox.Enabled = true;
                    _netState.PostProcessOption = NetworkState.PostProcess.ThresholdToBoolean;
                } else if (c == decimalsCheckBox) {
                    decimalsCheckBox.Enabled = true;
                    _netState.PostProcessOption = NetworkState.PostProcess.RoundToDecimal;
                } else if (c == winnerTakesAllCheckBox) {
                    winnerTakesAllCheckBox.Enabled = true;
                    _netState.PostProcessOption = NetworkState.PostProcess.WinnerTakesAll;
                }
            } else {
                thresholdCheckBox.Enabled = true;
                decimalsCheckBox.Enabled = true;
                winnerTakesAllCheckBox.Enabled = true;
                _netState.PostProcessOption = NetworkState.PostProcess.None;
            }
            processAndDisplayOutputValues();
        }

        private void copyToClipboardButton_Click(object sender, EventArgs e) {
            StringBuilder buffer = new StringBuilder();
            for (int i = 0; i < outputVectorsListView.Columns.Count; i++) {
                buffer.Append(outputVectorsListView.Columns[i].Text);
                buffer.Append("\t");
            }
            buffer.Append("\n");
            for (int i = 0; i < outputVectorsListView.Items.Count; i++) {
                for (int j = 0; j < outputVectorsListView.Columns.Count; j++) {
                    buffer.Append(outputVectorsListView.Items[i].SubItems[j].Text);
                    buffer.Append("\t");
                }
                buffer.Append("\n");
            }
            Clipboard.SetText(buffer.ToString());
        }

        //
        private void resetStopButton_Click(object sender, EventArgs e) {
            if (_netState.NetworkRunning)
                _netState.stopNetwork();
            else {
                _netState.resetNetwork();
                classifyButton.Enabled = false;
                validateButton.Enabled = false;
            }
        }

        //user closes form, stop the network if it is training or classifying
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e) {
            if (_netState != null) _netState.stopNetwork();
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
            saveAnxFileDialog.ShowDialog();
        }

        private void saveAnxFileDialog_FileOk(object sender, CancelEventArgs e) {
            if (_netState != null) _netState.NetworkInterface.saveNetwork(saveAnxFileDialog.FileName);
        }

    }
}
